了解 Performance Observer API 如何提供强大且非侵入式的方式来监控运行时 Web 性能,跟踪核心 Web vital,并为全球受众优化用户体验。
释放 Web 性能:深入剖析 Performance Observer API
在当今快节奏的数字世界中,Web 性能已不再是奢侈品,而是一种必需品。一个缓慢或无响应的网站会导致用户沮丧、跳出率升高,并直接对业务目标产生负面影响,无论这些目标是销售额、广告收入还是用户参与度。多年来,开发人员一直依赖于在单点时间测量性能的工具,通常在初始页面加载期间。虽然这很有用,但它忽略了故事的关键部分:用户与页面交互的整个体验。这就是运行时性能监控的用武之地,而其最强大的工具是 Performance Observer API。
传统方法通常涉及使用诸如 performance.getEntries() 之类的函数轮询性能数据。这可能效率低下,容易遗漏发生在轮询之间的关键事件,甚至会增加其试图测量的性能开销。Performance Observer API 通过提供一种异步、低开销的机制来订阅性能事件的发生,从而革新了这一过程。本指南将带您深入了解这个必不可少的 API,向您展示如何利用其强大功能来监控核心 Web Vitals,识别瓶颈,并最终为全球受众构建更快、更令人愉悦的 Web 体验。
什么是 Performance Observer API?
从本质上讲,Performance Observer API 是一个接口,提供了一种观察和收集性能测量事件(称为性能条目)的方法。可以将其视为浏览器内与性能相关的活动的专用侦听器。您不再主动询问浏览器“发生了什么事吗?”,浏览器会主动告诉您“发生了一个新的性能事件!以下是详细信息。”
这是通过观察者模式实现的。您创建一个观察者实例,告诉它您感兴趣的性能事件类型(例如,大型绘制、用户输入、布局偏移),并提供一个回调函数。每当在浏览器的性能时间轴中记录了指定类型的新事件时,您的回调函数都会被调用,其中包含新条目的列表。这种异步、基于推送的模型比旧的基于拉取的重复调用 performance.getEntries() 的模型更有效、更可靠。
旧方法与新方法
为了欣赏 Performance Observer 的创新,让我们对比一下这两种方法:
- 旧方法(轮询): 您可以使用 setTimeout 或 requestAnimationFrame 定期调用 performance.getEntriesByName('my-metric') 来查看您的指标是否已记录。这存在问题,因为您可能检查得太晚而错过了事件,或者检查得太频繁而浪费了 CPU 周期。如果您不定期清除条目,您还可能面临填满浏览器的性能缓冲区的风险。
- 新方法(观察): 您只需设置一次 PerformanceObserver。它静静地在后台运行,消耗最少的资源。只要记录了相关的性能条目,无论是在页面加载后的一毫秒还是用户会话的十分钟,您的代码都会立即收到通知。这可确保您永远不会错过任何事件,并且您的监控代码尽可能高效。
为什么您应该使用 Performance Observer
将 Performance Observer API 集成到您的开发工作流程中,可以为旨在实现全球覆盖的现代 Web 应用程序提供诸多好处。
- 非侵入式监控: 观察者的回调通常在空闲时段执行,从而确保您的性能监控代码不会干扰用户体验或阻塞主线程。它被设计为轻量级,并且性能影响可以忽略不计。
- 全面的运行时数据: Web 是动态的。性能问题不仅仅发生在加载时。用户可能会触发复杂的动画,通过滚动加载更多内容,或者在初始页面稳定后很久与重量级组件交互。Performance Observer 捕获这些运行时事件,为您提供整个用户会话的完整画面。
- 面向未来且标准化: 它是 W3C 推荐的收集性能数据的标准。新的性能指标和 API 旨在与其集成,使其成为您项目的可持续且具有前瞻性的选择。
- 实时用户监控 (RUM) 的基础: 要真正了解您的网站在不同国家/地区、设备和网络条件下的表现,您需要来自真实会话的数据。Performance Observer 是构建强大的 RUM 解决方案的理想工具,允许您收集重要的指标并将其发送到分析服务以进行聚合和分析。
- 消除竞争条件: 使用轮询,您可能尝试在记录性能条目之前访问它。观察者模型完全消除了这种竞争条件,因为您的代码仅在条目可用后才运行。
入门:Performance Observer 的基础知识
使用该 API 简单明了。该过程涉及三个主要步骤:创建观察者、定义回调以及告诉观察者要监视的内容。
1. 创建带有回调的观察者
首先,您实例化一个 PerformanceObserver 对象,并向其传递一个回调函数。每当检测到新条目时,都会执行此函数。
const observer = new PerformanceObserver((entryList) => { for (const entry of entryList.getEntries()) { console.log('Entry Type:', entry.entryType); console.log('Entry Name:', entry.name); console.log('Start Time:', entry.startTime); console.log('Duration:', entry.duration); } });
回调接收一个 PerformanceObserverEntryList 对象。您可以在此列表上调用 getEntries() 方法以获取所有新观察到的性能条目的数组。
2. 观察特定的条目类型
在您告诉它要监视的内容之前,观察者什么也不做。您可以使用 .observe() 方法执行此操作。此方法接受一个带有 entryTypes 属性的对象(或者在某些现代情况下,仅接受单个类型的 type),该属性是一个字符串数组,表示您感兴趣的性能条目类型。
// 开始观察两种类型的条目 observer.observe({ entryTypes: ['mark', 'measure'] });
一些最常见的条目类型包括:
- 'resource':有关网络请求的详细信息,例如脚本、图像和样式表。
- 'paint':首次绘制和首次内容绘制的时间。
- 'largest-contentful-paint':用于感知加载速度的核心 Web Vital 指标。
- 'layout-shift':用于视觉稳定性的核心 Web Vital 指标。
- 'first-input':有关第一次用户交互的信息,用于首次输入延迟核心 Web Vital。
- 'longtask':标识在主线程上运行超过 50 毫秒的任务,这可能导致无响应。
- 'mark' & 'measure':您使用 User Timing API 在自己的代码中定义的自定义标记和测量。
3. 停止观察者
当您不再需要收集数据时,最好断开观察者的连接以释放资源。
observer.disconnect();
实际用例:监控核心 Web Vitals
核心 Web Vitals 是 Google 认为在网页整体用户体验中很重要的一组特定因素。监控它们是 Performance Observer API 最强大的应用之一。让我们看看如何测量每一个。
监控最大内容绘制 (LCP)
LCP 测量加载性能。它标记了页面加载时间轴中主要内容可能已加载的点。良好的 LCP 分数是 2.5 秒或更短。
LCP 元素可能会随着页面加载而变化。最初,标题可能是 LCP 元素,但后来,更大的图像可能会加载并成为新的 LCP 元素。这就是 Performance Observer 如此完美的原因——它会在渲染时通知您每个潜在的 LCP 候选元素。
// 观察 LCP 并记录最终值 let lcpValue = 0; const lcpObserver = new PerformanceObserver((entryList) => { const entries = entryList.getEntries(); // 最后一个条目是最新的 LCP 候选元素 const lastEntry = entries[entries.length - 1]; lcpValue = lastEntry.startTime; console.log(`LCP updated: ${lcpValue.toFixed(2)}ms`, lastEntry.element); }); lcpObserver.observe({ type: 'largest-contentful-paint', buffered: true }); // 在用户交互后断开观察者的连接是好的做法, // 因为交互可能会阻止调度新的 LCP 候选元素。 // window.addEventListener('beforeunload', () => lcpObserver.disconnect());
注意使用 buffered: true。这是一个关键选项,它指示观察者包含在调用 observe() 方法*之前*记录的条目。这可以防止您错过早期的 LCP 事件。
监控首次输入延迟 (FID) 和下一次绘制交互 (INP)
这些指标衡量交互性。它们量化了用户第一次尝试与页面交互时的体验。
首次输入延迟 (FID) 测量用户首次与页面交互(例如,单击按钮)到浏览器实际开始处理响应于该交互的事件处理程序之间的时间。良好的 FID 为 100 毫秒或更短。
下一次绘制交互 (INP) 是一个更新、更全面的指标,它已于 2024 年 3 月取代 FID 作为核心 Web Vital。虽然 FID 仅测量*第一次*交互的*延迟*,但 INP 评估了整个页面生命周期中*所有*用户交互的*总延迟*,并报告最糟糕的一个。这可以更好地了解整体响应能力。良好的 INP 为 200 毫秒或更短。
您可以使用 'first-input' 条目类型来监控 FID:
// 观察 FID const fidObserver = new PerformanceObserver((entryList) => { for (const entry of entryList.getEntries()) { const fid = entry.processingStart - entry.startTime; console.log(`FID: ${fid.toFixed(2)}ms`); // 在报告第一个输入后断开连接 fidObserver.disconnect(); } }); fidObserver.observe({ type: 'first-input', buffered: true });
监控 INP 的过程略微复杂,因为它着眼于事件的完整持续时间。您观察 'event' 条目类型并计算持续时间,并跟踪最长的一个。
// 简化的 INP 监控示例 let worstInp = 0; const inpObserver = new PerformanceObserver((entryList) => { for (const entry of entryList.getEntries()) { // INP 是事件的持续时间 const inp = entry.duration; // 我们只关心比当前最差的交互 if (inp > worstInp) { worstInp = inp; console.log(`New worst INP: ${worstInp.toFixed(2)}ms`); } } }); inpObserver.observe({ type: 'event', durationThreshold: 16, buffered: true }); // durationThreshold 有助于过滤掉非常短、可能不重要的事件。
监控累积布局偏移 (CLS)
CLS 衡量视觉稳定性。它有助于量化用户体验意外布局偏移的频率——这是一种令人沮丧的体验,即内容在页面上未经警告地移动。良好的 CLS 分数是 0.1 或更少。
该分数是所有单独布局偏移分数的聚合。Performance Observer 在这里至关重要,因为它会报告发生的每个偏移。
// 观察并计算总 CLS 分数 let clsScore = 0; const clsObserver = new PerformanceObserver((entryList) => { for (const entry of entryList.getEntries()) { // 我们不想计算由用户输入引起的偏移 if (!entry.hadRecentInput) { clsScore += entry.value; console.log(`Current CLS score: ${clsScore.toFixed(4)}`); } } }); clsObserver.observe({ type: 'layout-shift', buffered: true });
hadRecentInput 属性很重要。它有助于过滤掉响应用户操作(如单击展开菜单的按钮)时发生的合法布局偏移,这不应计入 CLS 分数。
超越核心 Web Vitals:其他强大的条目类型
虽然核心 Web Vitals 是一个很好的起点,但 Performance Observer 可以监控更多内容。以下是其他一些非常有用的条目类型。
跟踪长时间任务 (`longtask`)
Long Tasks API 公开了占用主线程 50 毫秒或更长时间的任务。这些任务存在问题,因为当主线程繁忙时,页面无法响应用户输入,从而导致迟缓或冻结的体验。识别这些任务是提高 INP 的关键。
// 观察长时间任务 const longTaskObserver = new PerformanceObserver((entryList) => { for (const entry of entryList.getEntries()) { console.log(`Long Task Detected: ${entry.duration.toFixed(2)}ms`); // 'attribution' 属性有时可以告诉您是什么导致了长时间任务 console.log('Attribution:', entry.attribution); } }); longTaskObserver.observe({ type: 'longtask', buffered: true });
分析资源计时 (`resource`)
了解资产的加载方式对于性能调整至关重要。'resource' 条目类型为您提供页面上每个资源的详细网络计时数据,包括 DNS 查找、TCP 连接和内容下载时间。
// 观察资源计时 const resourceObserver = new PerformanceObserver((entryList) => { for (const entry of entryList.getEntries()) { // 让我们查找加载缓慢的图像 if (entry.initiatorType === 'img' && entry.duration > 500) { console.warn(`Slow image detected: ${entry.name}`, `Duration: ${entry.duration.toFixed(2)}ms`); } } }); // 对于资源计时,使用 'buffered: true' 几乎总是必要的, // 以捕获在此脚本运行之前加载的资产。 resourceObserver.observe({ type: 'resource', buffered: true });
测量自定义性能标记 (`mark` 和 `measure`)
有时,您需要测量特定于应用程序的逻辑的性能。User Timing API 允许您创建自定义时间戳并测量它们之间的时间。
- performance.mark('start-operation'): 创建名为 'start-operation' 的时间戳。
- performance.mark('end-operation'): 创建另一个时间戳。
- performance.measure('my-operation', 'start-operation', 'end-operation'): 在两个标记之间创建测量。
Performance Observer 可以侦听这些自定义的 'mark' 和 'measure' 条目,这非常适合收集有关 JavaScript 框架中组件渲染时间或关键 API 调用及其后续数据处理等内容的计时数据。
// 在您的应用程序代码中: performance.mark('start-data-processing'); // ... 一些复杂的数据处理 ... performance.mark('end-data-processing'); performance.measure('data-processing-duration', 'start-data-processing', 'end-data-processing'); // 在您的监控脚本中: const customObserver = new PerformanceObserver((entryList) => { for (const entry of entryList.getEntriesByName('data-processing-duration')) { console.log(`Custom Measurement '${entry.name}': ${entry.duration.toFixed(2)}ms`); } }); customObserver.observe({ entryTypes: ['measure'] });
高级概念和最佳实践
要在专业的生产环境中有效地使用 Performance Observer API,请考虑以下最佳实践。
- 始终考虑 `buffered: true`:对于可能在页面加载早期发生的条目类型(如 'resource'、'paint' 或 'largest-contentful-paint'),使用 buffered 标志对于避免遗漏它们至关重要。
- 检查浏览器支持: 虽然现代浏览器广泛支持它,但在使用它之前,最好始终检查它是否存在。您还可以检查特定浏览器支持哪些条目类型。
- if ('PerformanceObserver' in window && PerformanceObserver.supportedEntryTypes.includes('longtask')) { // 可以安全地将 PerformanceObserver 用于长时间任务 }
- 将数据发送到分析服务: 将数据记录到控制台对于开发来说非常棒,但对于实际监控,您需要聚合此数据。从客户端发送此遥测数据的最佳方法是使用 navigator.sendBeacon() API。这是一种非阻塞机制,旨在将少量数据发送到服务器,并且即使正在卸载页面,它也能可靠地工作。
- 按关注点对观察者进行分组: 虽然您可以使用单个观察者来处理多种条目类型,但为不同的关注点(例如,一个用于核心 Web Vitals,一个用于资源计时,一个用于自定义指标)创建单独的观察者通常更清晰。这可以提高代码的可读性和可维护性。
- 了解性能开销: API 旨在具有非常低的开销。但是,执行繁重计算的非常复杂的回调函数可能会影响性能。保持您的观察者回调精简高效。将任何繁重的处理推迟到 Web Worker,或将原始数据发送到您的后端以在那里进行处理。
结论:构建以性能为先的文化
Performance Observer API 不仅仅是另一个工具;它是我们处理 Web 性能方式的根本转变。它将我们从被动的、一次性的测量转变为主动的、持续的监控,反映了我们在全球范围内用户的真实、动态体验。通过提供一种可靠而有效的方式来捕获核心 Web Vitals、长时间任务、资源计时和自定义指标,它使开发人员能够识别和解决性能瓶颈,然后再影响大量用户。
在任何开发团队中采用 Performance Observer API 是构建以性能为先的文化的重要一步。当您可以衡量重要内容时,您就可以改进重要内容。立即开始将这些观察者集成到您的项目中。您的用户——无论他们在世界的哪个地方——都会感谢您带来更快、更流畅、更令人愉悦的体验。